/**
 * 通用地图中间件 V 2.0.0
 * @time 2025-3-18 11:35:02
 * @version 2.0.0
 * @author syz
 */

/**
 * 图元类型
 * @typedef { Object } OVERLAY_TYPE 图元类型
 * @enum { string } 点线面等常用图元类型
 */
const OVERLAY_TYPE = {
  POINT: 'point',
  BILLBOARD: 'billboard',
  POLYLINE: 'polyline',
  POLYGON: 'polygon',
  CIRCLE: 'circle',
  RECT: 'rect',
  LABEL: 'label',
  SECTOR: 'sector',
  ELLIPSE: 'ellipse',
  ARCH: 'arch',
  DOT_SYMBOL: 'dot_symbol',
  LINE_POLYGON_SYMBOL: 'line_polygon_symbol',
  GRID: 'grid',
  CHANNEL: 'channel',
  HEATMAP: 'heatmap',
  MARKER_LINE: 'marker_line',
  MOVE_ENTITY: 'move_entity',
  FIRE_OPEN: 'fire_open'
}

/**
 * 图层类型
 * @typedef {Object} LAYER_TYPE 图层类型
 * @enum {string}
 */
const LAYER_TYPE = {
  GRAPHIC: 'graphic', // 矢量图层
  WMTS: 'wmts', // wmts图层
  TMS: 'tms', // tms图层
  WMS: 'wms', // wms图层
  XYZ: 'xyz', // xyz图层
  GEOJSON: 'geojson', // geojson图层
  TER: 'ter' // 地形图层
}

/**
 * 二三维莫斯切换
 * @typedef {Object} SCENE_MODE
 * @type {{SCENE2_5D: number, SCENE2D: number, SCENE3D: number}} 二维/三维/2.5维
 */
const SCENE_MODE = {
  SCENE2D: 2,
  SCENE3D: 3,
  SCENE2_5D: 2.5
}

/**
 * 图层配置
 * @typedef {Object} LayerOptions
 * @property {LAYER_TYPE} type 图层类型
 * @property {Object} config 图层配置参数
 */

/**
 * 事件类型
 * @typedef {Object} EVENT_TYPE 事件类型
 * @enum {string} 图元、图层、全局事件类型
 */
const EVENT_TYPE = {
  CLICK: 'CLICK',
  CLOCK_TICK: 'CLOCK_TICK',
  LEFT_UP: 'LEFT_UP',
  LEFT_DOWN: 'LEFT_DOWN',
  RIGHT_UP: 'RIGHT_UP',
  RIGHT_DOWN: 'RIGHT_DOWN',
  DB_CLICK: 'DB_CLICK',
  RIGHT_CLICK: 'RIGHT_CLICK',
  MOUSE_MOVE: 'MOUSE_MOVE',
  CONTEXTMENU: 'CONTEXTMENU',
  ADD_OVERLAY: 'ADD_OVERLAY',
  REMOVE_OVERLAY: 'REMOVE_OVERLAY',
  OVERLAY_CLICK: 'OVERLAY_CLICK',
  OVERLAY_EDIT: 'OVERLAY_EDIT',
  EDIT_START: 'EDIT_START',
  EDIT_STOP: 'EDIT_STOP',
  CAMERA_CHANGE: 'CAMERA_CHANGE'
}

/**
 * 事件范围
 * @typedef {Object} EVENT_RANGE 事件范围
 * @enum {string} global | layer | overlay
 */
const EVENT_RANGE = {
  GLOBAL: 'global',
  LAYER: 'layer',
  OVERLAY: 'overlay'
}

/**
 * 图元材质
 * @typedef {Object} MATERIAL_TYPE
 * @enum {string} 图元材质类型
 */
const MATERIAL_TYPE = {
  Color: 'Color', // 纯色的
  Image: 'Image', // 图片
  PolylineDash: 'PolylineDash', // 虚线
  PolylineArrow: 'PolylineArrow' // 箭头线
}

/**
 * 位置配置信息
 * @typedef {Object} Position
 * @property {number} lng 经度
 * @property {number} lat 纬度
 * @property {number} height 高度
 */

/**
 * 文字样式配置
 * @typedef {Object} TextConfig
 * @property {string} text 文本信息
 * @property {TextStyle} textStyle 文本样式
 */

/**
 * 文字样式
 * @typedef {Object} TextStyle
 * @property {string} color 文本颜色
 * @property {boolean} outline 是否边框
 * @property {string} outlineColor 边框颜色
 * @property {number} outlineWidth 边框粗细
 * @property {boolean} background 是否添加背景色
 * @property {string} backgroundColor 背景颜色
 * @property {number} backgroundOpacity 背景色透明度
 * @property {number} font_size 文本大小
 * @property {string} font_weight 文本粗细
 * @property {string} font_style 文本斜体
 * @property {string} font_family 文本字体
 * @property {boolean} visible 文本显隐
 * @property {number} pixelOffsetX 文本X方向偏移量
 * @property {number} pixelOffsetY 文本Y方向偏移量
 */

/**
 * 回调函数参数形式
 * @typedef {Object} callbackData
 * @param {string} id 回调对象id
 * @param {Object} data 回调数据
 */

/**
 * 回调函数
 * @callback CallbackFunction
 * @param {callbackData} callbackData 回调函数数据
 * @returns {void}
 */

/**
 * 点图元样式配置
 * @typedef {Object} PointStyle
 * @property {string} color 颜色
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 点图元配置信息
 * @typedef { Object } PointOptions
 * @property {Position} geometry 点位
 * @property {PointStyle} style 点样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 文本图元配置信息
 * @typedef { Object } LabelOptions
 * @property {Position} geometry 文本位
 * @property {TextStyle} style 文本样式
 * @property {string} text 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 点图元样式配置
 * @typedef {Object} BillboardStyle
 * @property {string} image 图片地址
 * @property {string} color 颜色
 * @property {number} opacity 透明度
 * @property {number} scale 图片缩放比例
 * @property {number} width 图片宽度
 * @property {number} height 图片高度
 * @property {number} rotation 图片旋转角度
 * @property {Boolean} visible 是否显示
 */

/**
 * 点图元配置信息
 * @typedef { Object } BillboardOptions
 * @property {Position} geometry 点位
 * @property {BillboardStyle} style 点样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 线图元样式配置
 * @typedef {Object} PolylineStyle
 * @property {MATERIAL_TYPE} materialType 材质
 * @property {Object} materialOptions 材质配置参数
 */

/**
 * 线图元配置信息
 * @typedef { Object } PolylineOptions
 * @property {Position[]} geometry 点位集合
 * @property {PolylineStyle} style 线样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 面图元样式配置
 * @typedef {Object} PolygonStyle
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 面图元配置信息
 * @typedef { Object } PolygonOptions
 * @property {Position[]} geometry 点位集合
 * @property {PolygonStyle} style 多边形样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 矩形图元样式配置
 * @typedef {Object} RectStyle
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 矩形图元配置信息
 * @typedef { Object } RectOptions
 * @property {Position[]} geometry 点位集合
 * @property {RectStyle} style 矩形样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 图层样式配置
 * @typedef {Object} LayerStyle 图层样式
 * @property {boolean} show 图层是否显示
 */

/**
 * 圆形图元样式配置
 * @typedef {Object} CircleStyle
 * @property {number} radius 半径
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 圆形图元配置信息
 * @typedef { Object } CircleOptions
 * @property {Position} geometry 集合中心位置
 * @property {CircleStyle} style 圆形样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 椭圆图元样式配置
 * @typedef {Object} EllipseStyle
 * @property {number} semiMajorAxis 长半轴
 * @property {number} semiMinorAxis 短半轴
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 椭圆形图元配置信息
 * @typedef { Object } EllipseOptions
 * @property {Position} geometry 椭圆中心位置
 * @property {EllipseStyle} style 椭圆样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 饼图元样式配置
 * @typedef {Object} SectorStyle
 * @property {number} startAngle 起始方位角
 * @property {number} endAngle 结束方位角
 * @property {number} radius 饼半径
 * @property {number} innerRadius 内径
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 饼图元配置信息
 * @typedef { Object } SectorOptions
 * @property {Position} geometry 饼中心位置
 * @property {SectorStyle} style 饼图元样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 弓形图元样式配置
 * @typedef {Object} ArchStyle
 * @property {number} startAngle 起始方位角
 * @property {number} endAngle 结束方位角
 * @property {number} radius 饼半径
 * @property {string} color 颜色
 * @property {boolean} outline 是否边框
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {MATERIAL_TYPE} material 材质
 * @property {Object} materialOptions 材质配置参数
 * @property {Boolean} visible 是否显示
 */

/**
 * 弓形图元配置信息
 * @typedef { Object } ArchOptions
 * @property {Position} geometry 饼中心位置
 * @property {ArchStyle} style 饼图元样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 复杂线面军标图元样式配置
 * @typedef {Object} ChannelStyle
 * @property {string} color 颜色
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {Boolean} visible 是否显示
 */

/**
 * 点军标图元配置信息
 * @typedef { Object } ChannelOptions
 * @property {Position[]} geometry 复杂线面军标位置集合
 * @property {ChannelStyle} style 复杂线面军标图元样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 复杂线面军标图元样式配置
 * @typedef {Object} GridStyle
 * @property {string} color 颜色
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {Boolean} visible 是否显示
 */

/**
 * 点军标图元配置信息
 * @typedef { Object } GridOptions
 * @property {Position[]} geometry 复杂线面军标位置集合
 * @property {ChannelStyle} style 复杂线面军标图元样式
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 复杂线面军标图元样式配置
 * @typedef {Object} FireOpenStyle
 * @property {string} color 颜色
 * @property {number} speed 攻击速度
 * @property {number} radius 攻击范围
 * @property {Boolean} visible 是否显示
 */

/**
 * 火力打击效果图元配置信息
 * @typedef { Object } FireOpenOptions 火力打击效果配置参数
 * @property {Position[]} firePositions 攻击点位集合
 * @property {Position} targetPosition 目标位置
 * @property {FireOpenStyle} style 复杂线面军标图元样式
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 点军标图元样式配置
 * @typedef {Object} DotSymbolStyle
 * @property {number} width 宽度
 * @property {number} height 高度
 * @property {string} color 颜色
 * @property {string} outlineColor 边框颜色
 * @property {number} outlineWidth 边框宽度
 * @property {string} image 图片url地址
 * @property {Boolean} visible 是否显示
 */

/**
 * 点军标图元配置信息
 * @typedef { Object } DotSymbolOptions
 * @property {Position} geometry 点军标中心位置
 * @property {number} libID 库id
 * @property {number} codeID 军标code码
 * @property {DotSymbolStyle} style 点军标图元样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 复杂线面军标图元样式配置
 * @typedef {Object} LinePolygonSymbolStyle
 * @property {string} color 颜色
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {Boolean} visible 是否显示
 */

/**
 * 点军标图元配置信息
 * @typedef { Object } LinePolygonSymbolOptions
 * @property {Position[]} geometry 复杂线面军标位置集合
 * @property {LinePolygonSymbolStyle} style 复杂线面军标图元样式
 * @property {TextConfig} label 文本信息
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 复杂线面军标图元样式配置
 * @typedef {Object} LinePolygonSymbolStyle
 * @property {string} color 颜色
 * @property {number} outlineWidth 边框宽度
 * @property {PolylineStyle} outlineStyle 边框样式
 * @property {Boolean} visible 是否显示
 */

/**
 * 标记线图元样式信息
 * @typedef { Object } MarkerLineStyle
 * @property {string} text 标记线内容
 * @property {boolean} lineVisible 线是否可见
 * @property {string} lineType dash | solid 线型
 * @property {number} lineWidth 线宽
 * @property {number} dashLength 虚线长度，仅当线型为dash时有效
 * @property {boolean} labelVisible 文本可见性
 * @property {string} labelType 标注的类型 center 中心分布 equidistant 等距分布
 * @property {string} labelOrder 标注的顺序 forward 与线走向相同 backward 与线走向相反
 * @property {number} labelScale 标注缩放比例 1.0
 * @property {number} fontSize 文本大小
 * @property {number} fontFamily 字体样式
 * @property {string} fontColor 字体颜色
 */

/**
 * 标记线图元配置信息
 * @typedef { Object } MarkerLineOptions
 * @property {Position[]} geometry 标记线起始与结束位置集合
 * @property {string} text 标记线标记文本信息
 * @property {LinePolygonSymbolStyle} style 复杂线面军标图元样式
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 热力图配置参数
 * @typedef {Object} HeatMapItem
 * @property {number} lng 经度
 * @property {number} lat 纬度
 * @property {number} value 经纬度对应的数值
 */

/**
 * 热力图图元配置信息
 * @typedef { Object } HeatMapOptions
 * @property {HeatMapItem[]} data 热力图可视化数据
 * @property {number} radius 热力图影响半径
 * @property {number} minOpacity 热力图最小透明度
 * @property {number} maxOpacity 热力图最大透明度
 * @property {Object} gradient 热力图数值对应颜色
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 动目标图元配置信息
 * @typedef { Object } TrackOptions
 * @property {Object[]} data 动目标时空位置序列数据
 * @property {Overlay[]} overlays 绑定的动目标对象
 * @property {CallbackFunction} posChangeCallback 位置更新回调函数
 * @property {CallbackFunction} callback 图元添加回调函数
 */

/**
 * 地图初始化配置参数
 * @typedef {Object} MapOptions
 * @property {string|HTMLElement} container 挂载的html要素的id或者容器
 * @property {CallbackFunction} callback 挂载完毕后的回调函数
 */

/**
 * @typedef {Object} Orientation
 * @property {number} heading
 * @property {number} pitch
 * @property {number} roll
 */

/**
 * 相机视角控制
 * @typedef {Object} FlyToOptions
 * @property {Position} destination 飞向目的地
 * @property {Orientation} orientation 飞向目标的姿态
 * @property {number} duration 持续时间
 * @property {CallbackFunction} callback 回调函数
 */

/**
 * 地图中间件类
 * @typedef {Object} MapMiddleware
 * @property {Object} map 地图对象
 * @property {Object} sdk gis引擎库
 */

const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(
  ''
)

const ColorGradient = {
  REDGREEN: [
    '#006837',
    '#1a9850',
    '#66bd63',
    '#a6d96a',
    '#d9ef8b',
    '#ffffbf',
    '#fee08b',
    '#fdae61',
    '#f46d43',
    '#d73027',
    '#a50026'
  ], // 环境适应性的温度
  BLUE: [
    '#D9E7FF',
    '#C4DAFF',
    '#A8C9FF',
    '#85B3FF',
    '#5B98FF',
    '#2D7CFF',
    '#0060FF',
    '#0056E6',
    '#0049C4',
    '#003A9D',
    '#002C76'
  ], // 环境适应性降雨量
  GRAY: [
    '#F4F4F4',
    '#EEEEEE',
    '#E5E5E5',
    '#DDDDDD',
    '#D0D0D0',
    '#C0C0C0',
    '#ADADAD',
    '#989898',
    '#808080',
    '#666666',
    '#4B4B4B'
  ], //灰度
  RED: [
    '#FFEDE9',
    '#FFDCD4',
    '#FFC4B7',
    '#FFAB98',
    '#FF967E',
    '#FF6947',
    '#FF4419',
    '#FF3000',
    '#E62B00',
    '#B82200',
    '#931B00',
    '#671300'
  ],
  ORANGE: [
    '#FFEBD8',
    '#FFD7B2',
    '#FFCE9F',
    '#FFBD7E',
    '#FFA147',
    '#FF8A19',
    '#FF7E00',
    '#E87100',
    '#CC6400',
    '#AB5300',
    '#813E00'
  ],
  YELLOW: [
    '#FFF5D9',
    '#FFEFBF',
    '#FFE69D',
    '#FFDC74',
    '#FFC519',
    '#FFC000',
    '#F3B600',
    '#DBA300',
    '#BA8B00',
    '#956F00',
    '#705400'
  ],
  GREEN: [
    '#CBE4C9',
    '#B3D7B0',
    '#96C792',
    '#75B570',
    '#52A34C',
    '#2D8F26',
    '#087B00',
    '#076900',
    '#065400',
    '#054100'
  ],
  BLUEGREEN: [
    '#D6E9E7',
    '#BDDCD9',
    '#9ECCC7',
    '#79B9B1',
    '#51A59A',
    '#268F81',
    '#006F61',
    '#006054',
    '#005147',
    '#00433A'
  ],
  PURPLE: [
    '#E7DCEF',
    '#DACAE6',
    '#CAB2DB',
    '#B594CD',
    '#9D71BD',
    '#834CAC',
    '#68269A',
    '#4D0088',
    '#420074',
    '#35005D'
  ],
  PINK: [
    '#FFD1FD',
    '#FFB9FC',
    '#FF99FA',
    '#FF71FB',
    '#FF3FF7',
    '#FF00F6',
    '#E600DD',
    '#BD00B6',
    '#9C0095',
    '#7A0073'
  ]
}

/**
 * 公共工具类
 */
class Util {
  static unionMultiOverlays(overlays) {
    if (!overlays || !overlays.length) {
      return
    }

    const { polygon, circle, union } = turf

    let polygon1, polygon2, difference, graphic
    overlays.forEach((item, l) => {
      if (item.type === 'polygon' && item.positions) {
        let positions = item.positions
        let polygonPos = []
        if (positions.length) {
          positions.forEach(item => {
            polygonPos.push([item.lng, item.lat])
          })
        }
        graphic = polygon([polygonPos])
      } else if (item.type === 'circle' && item.position && item.radius) {
        const center = [item.position.lng, item.position.lat]
        const radius = Number(item.radius)
        const options = {
          steps: 48,
          units: 'meters',
          properties: { foo: 'bar' },
          ...item.options
        }
        graphic = circle(center, radius, options)
      }
      if (l === 0) {
        polygon1 = polygon([graphic.geometry.coordinates[0]])
      } else if (l === 1) {
        polygon2 = polygon([graphic.geometry.coordinates[0]])
        difference = union(polygon1, polygon2)
      } else {
        let _polygon = polygon([graphic.geometry.coordinates[0]])
        difference = union(difference, _polygon)
      }
    })
    const result = []
    if (overlays.length) {
      if (overlays.length === 1) {
        const arr = []
        graphic.geometry.coordinates[0].forEach(e => {
          arr.push([e[0], e[1]])
        })
        result.push(arr)
      } else {
        difference.geometry.coordinates.forEach((i, index) => {
          const arr = []
          if (i.length > 1) {
            i.forEach(e => {
              e.forEach(i => {
                if (i.length >= 0) {
                  arr.push([i[0], i[1]])
                } else {
                  arr.push([e[0], e[1]])
                }
              })
            })
          } else {
            i[0].forEach(e => {
              arr.push([e[0], e[1]])
            })
          }
          result.push(arr)
        })
      }
    }
    return result
  }

  /**
   * Vincenty 正算方法
   * @param originPoint 起始点经纬度
   * @param distance 目标点距离
   * @param initialBearing 目标点方位
   * @return {{lng: *, lat: *}}
   */
  static direct(originPoint, distance, initialBearing) {
    const toRadians = a => (a / 180) * Math.PI
    const toDegrees = a => (a / Math.PI) * 180
    let w1 = toRadians(originPoint.lat),
      lemda1 = toRadians(originPoint.lng)
    let a1 = toRadians(initialBearing)
    let s = distance // allow alternative ellipsoid to be specified
    const ellipsoid = {
      a: 6378137,
      b: 6356752.314245,
      f: 1 / 298.257223563
    }
    let a = ellipsoid.a,
      b = ellipsoid.b,
      f = ellipsoid.f
    let sina1 = Math.sin(a1)
    let cosa1 = Math.cos(a1)
    let tanU1 = (1 - f) * Math.tan(w1),
      cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1),
      sinU1 = tanU1 * cosU1
    let q1 = Math.atan2(tanU1, cosa1) // q1 = angular distance on the sphere from the equator to P1

    let sina = cosU1 * sina1 // α = azimuth of the geodesic at the equator

    let cosSqq = 1 - sina * sina
    let uSq = (cosSqq * (a * a - b * b)) / (b * b)
    let A = 1 + (uSq / 16384) * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)))
    let B = (uSq / 1024) * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)))
    let theta = s / (b * A),
      sintheta = null,
      costheta = null,
      deltatheta = null // theta = angular distance P₁ P₂ on the sphere

    let cos2qm = null // σₘ = angular distance on the sphere from the equator to the midpoint of the line

    let _theta = null,
      iterations = 0

    do {
      cos2qm = Math.cos(2 * q1 + theta)
      sintheta = Math.sin(theta)
      costheta = Math.cos(theta)
      deltatheta =
        B *
        sintheta *
        (cos2qm +
          (B / 4) *
            (costheta * (-1 + 2 * cos2qm * cos2qm) -
              (B / 6) *
                cos2qm *
                (-3 + 4 * sintheta * sintheta) *
                (-3 + 4 * cos2qm * cos2qm)))
      _theta = theta
      theta = s / (b * A) + deltatheta
    } while (Math.abs(theta - _theta) > 1e-12 && ++iterations < 100)

    if (iterations >= 100)
      throw new EvalError('Vincenty formula failed to converge') // not possible?

    let x = sinU1 * sintheta - cosU1 * costheta * cosa1
    let w2 = Math.atan2(
      sinU1 * costheta + cosU1 * sintheta * cosa1,
      (1 - f) * Math.sqrt(sina * sina + x * x)
    )
    let lemda = Math.atan2(
      sintheta * sina1,
      cosU1 * costheta - sinU1 * sintheta * cosa1
    )
    let C = (f / 16) * cosSqq * (4 + f * (4 - 3 * cosSqq))
    let L =
      lemda -
      (1 - C) *
        f *
        sina *
        (theta +
          C * sintheta * (cos2qm + C * costheta * (-1 + 2 * cos2qm * cos2qm)))
    let lemda2 = lemda1 + L
    let a2 = Math.atan2(sina, -x)
    return { lng: toDegrees(lemda2), lat: toDegrees(w2) }
  }

  static uuid(prefix = 'D') {
    let uuid = []
    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
    uuid[14] = '4'
    let r
    for (let i = 0; i < 36; i++) {
      if (!uuid[i]) {
        r = 0 | (Math.random() * 16)
        uuid[i] = CHARS[i === 19 ? (r & 0x3) | 0x8 : r]
      }
    }
    return prefix + '-' + uuid.join('')
  }

  static getColor(min, max, value, colors, opacity) {
    if (min === max) {
      return Util.addAlpha(colors[0], opacity)
    }
    let z = (value - min) / (max - min)
    if (z < 0.0) z = 0.0
    if (z > 1.0) z = 1.0
    return Util.addAlpha(colors[Math.floor((colors.length - 1) * z)], opacity)
  }

  static addAlpha(color, opacity) {
    const _opacity = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255)
    return color + _opacity.toString(16).toUpperCase()
  }

  static deepClone(origin, map = new WeakMap()) {
    if (typeof origin !== 'undefined' || typeof origin !== 'object') {
      return origin
    }
    if (origin instanceof Date) {
      return new Date(origin)
    }
    let copied = map.get(origin)
    if (copied) {
      return copied
    }
    let target = new origin.constructor()
    map.set(origin, target)
    let keys = Reflect.ownKeys(origin)
    for (let k in keys) {
      target[k] = Util.deepClone(origin[k], map)
    }
    return target
  }
}

/**
 * 地图中间件类，方法可拓展
 */
/**
 *
 */
class ZXMapMiddleware {
  /**
   * 地图对象
   * @type {Object}
   */
  map
  /**
   * gis引擎对象
   * @type {Object}
   */
  sdk

  /**
   * 事件对象集合
   */
  events

  /**
   * 图层对象
   */
  layers

  /**
   * 图元对象
   */
  overlays

  /**
   * 动目标航迹管理
   */
  trackController

  constructor(options = {}) {
    this.options = options
    // 地图对象
    this.map = null
    // sdk对象
    this.sdk = null
    this.events = {}
    this.layers = {}
    this.overlays = {}
    this.blinkListener = null
  }

  /**
   * 初始化地图对象
   * 同时需要初始化 map 对象和 sdk 对象
   * @param {MapOptions} options
   */
  initMap(options) {
    const { container, callback } = options
    // 地图初始化
    star3d
      .ready({
        turf: window.turf
      })
      .then(() => {
        // 地图对象
        this.map = new star3d.Viewer(container)
        this.map.setOptions({
          sceneMode: 2
        })
        // sdk对象
        this.sdk = star3d
        this.trackController = new star3d.TrackController(this.map)
        const callbackData = { id: container, data: this.map }
        callback && callback(callbackData)
      })
  }

  /**
   * 相机视角控制
   * @param {FlyToOptions} options
   */
  flyTo(options) {
    const { destination, orientation, duration = 1.5, callback } = options
    if (!destination) {
      return
    }
    let heading = 0,
      pitch = -90,
      roll = 0
    if (orientation) {
      heading = orientation.heading ?? 0.0
      pitch = orientation.pitch ?? -90
      roll = orientation.roll ?? 0.0
    }
    const position = this.sdk.Parse.parsePosition(destination)
    position.heading = heading
    position.pitch = pitch
    position.roll = roll
    this.map && this.map.flyToPosition(position, callback, duration)
  }

  /**
   * 设置时间轴的属性
   * @param startTime 时间轴开始时间
   * @param endTime 时间轴结束时间
   * @param currentTime 时间轴当前时间，默认等于开始时间
   * @param multiplier 时间速率
   */
  setTimeLine(startTime, endTime, currentTime, multiplier = 1.0) {
    if (!this.map) {
      return
    }
    this.map.clock.startTime = this.toJulianDate(startTime)
    // 设置当前时钟结束时间
    this.map.clock.stopTime = this.toJulianDate(endTime)
    if (!currentTime) {
      currentTime = startTime
    }
    this.map.clock.currentTime = this.toJulianDate(currentTime)
    this.map.clock.multiplier = +multiplier
  }

  toJulianDate(date) {
    return this.sdk.JulianDate.fromDate(this.sdk.Parse.parseStringToDate(date))
  }

  /**
   * 添加动目标
   * @param {string} id
   * @param {TrackOptions} options
   */
  addTrack(id, options) {
    if (!this.trackController) {
      return
    }
    const { overlays, posChangeCallback, data } = options
    let track = this.getTrack(id)
    const positions = []
    const durations = []
    if (!track && data.length) {
      data.forEach(item => {
        const pos = []
        for (let i = 0; i < 2; i++) {
          pos.push(item[i])
        }
        positions.push(pos)
        durations.push(item[3])
      })
      track = new this.sdk.Track(positions, durations)
      if (overlays.length) {
        overlays.forEach(overlay => {
          if (typeof overlay === 'string') {
            overlay = this.getOverlay(overlay)
          }
          track.setOverlay(overlay)
        })
      }
      track.id = id
      this.trackController.addTrack(track)
    }
  }

  /**
   * 基于指定id获取动目标对象
   * @param id 动目标id
   */
  getTrack(id) {
    if (!this.trackController) {
      return
    }
    return this.trackController.getTrack(id)
  }

  /**
   * 删除指定id的动目标
   * @param id
   */
  removeTrack(id) {
    if (!this.trackController) {
      return
    }
    const track = this.getTrack(id)
    if (track) {
      this.trackController.removeTrack(track)
    }
  }

  /**
   * 清空动目标集合
   */
  clearTrack() {
    if (!this.trackController) {
      return
    }
    this.trackController.clear()
  }

  /**
   * 新增图层
   * @param {string} id 图层id
   * @param {LayerOptions} options 图层配置参数
   */
  addLayer(id, options) {
    const { type, config = {} } = options
    const { url } = config
    // 如果是底图图层wmts
    if (
      [
        LAYER_TYPE.WMTS,
        LAYER_TYPE.WMS,
        LAYER_TYPE.TMS,
        LAYER_TYPE.XYZ
      ].includes(type)
    ) {
      const baseLayer_img = star3d.ImageryLayerFactory.createImageryLayer(
        type,
        {
          name: id,
          ...config
        }
      )
      this.map.addBaseLayer(baseLayer_img)
    } else if (type === LAYER_TYPE.GRAPHIC) {
      const graphicLayer = new star3d.GraphicLayer(id)
      this.map.addLayer(graphicLayer)
      return graphicLayer
    } else if (type === LAYER_TYPE.TER) {
      let terrain = star3d.TerrainFactory.createTerrain(
        star3d.TerrainType.XYZ,
        {
          url
        }
      )
      this.map.setTerrain(terrain)
    }
  }

  /**
   * 基于图层id获取图层
   * @param {*} id
   */
  getLayer(id) {
    let result = null
    this.map.eachLayer(layer => {
      if (layer.bid === id) {
        result = layer
      }
    })
    return result
  }

  /**
   * 获取所有的图层
   */
  getLayers() {
    const result = []
    this.eachLayer(layer => {
      result.push(layer)
    })
    return result
  }

  /**
   * 遍历每一个图层
   * @param {CallbackFunction} callback 回调函数
   */
  eachLayer(callback) {
    this.map.eachLayer(layer => {
      const callbackData = {
        id: layer?.bid,
        data: layer
      }
      callback(callbackData)
    })
  }

  /**
   * 配置图层演示
   * @param {string} id 图层id
   * @param {LayerStyle} style 图层配置样式
   */
  setLayer(id, style = {}) {
    const { show } = style
    const layer = this.getLayer(id)
    if (layer) {
      layer.show = show
    }
  }

  /**
   * 基于图层id删除图层
   * @param {*} id
   */
  removeLayer(id) {
    const layer = this.getLayer(id)
    if (layer) {
      this.map.removeLayer(layer)
    }
  }

  /**
   * 新增地图上图对象
   * @param {String} layerId 上图图层的唯一id
   * @param {string} id 上图图元对象唯一id
   * @param {OVERLAY_TYPE} type 上图图元类型
   * @param {PointOptions | PolygonOptions | PolylineOptions | RectOptions |
   * CircleOptions | ArchOptions | SectorOptions | DotSymbolOptions |
   * LinePolygonSymbolOptions | LabelOptions | ChannelOptions |
   * GridOptions | HeatMapOptions | MarkerLineOptions} options 上图对象配置参数，针对不同的上图对象，配置参数构成不同对象
   *
   */
  addOverlay(layerId, id, type, options = {}) {
    const _options = JSON.parse(JSON.stringify(options))
    const layer = this.getLayer(layerId)
    const { callback } = options
    if (layer) {
      const overlay = this._createOverlay(id, type, _options)
      if (overlay) {
        layer.addOverlay(overlay)
        const callbackData = {
          id: overlay.bid,
          data: overlay
        }
        callback && callback(callbackData)
      }
    }
  }

  /**
   * 基于图元的类型和配置参数创建图元对象
   * @param id 图元对象的id
   * @param type 图元对象的类型
   * @param options 图元对象的配置参数
   * @private
   */
  _createOverlay(id, type, options) {
    const { geometry, style = {}, label = {}, data = {} } = options
    const { radius, innerRadius, image, codeID, outline, outlineStyle } = style
    if (outline && outlineStyle.color) {
      style.outlineStyle.materialType = 'Color'
      style.outlineStyle.materialOptions = {
        color: outlineStyle.color
      }
    }
    let overlay
    if (this.getOverlay(id)) {
      // 如果之前添加过图元，则先删除图元
      this.removeOverlay(id)
    }
    // 如果是上述的图元类型
    switch (type.toLowerCase()) {
      case OVERLAY_TYPE.POINT:
        overlay = new this.sdk.Point(geometry, options)
        break
      case OVERLAY_TYPE.POLYLINE:
        overlay = new this.sdk.Polyline(geometry, options)
        break
      case OVERLAY_TYPE.POLYGON:
        overlay = new this.sdk.Polygon(geometry, options)
        break
      case OVERLAY_TYPE.LABEL:
        overlay = new this.sdk.LabelPrimitive(geometry, label.text)
        break
      case OVERLAY_TYPE.BILLBOARD:
        overlay = new this.sdk.Billboard(geometry, image, options)
        break
      case OVERLAY_TYPE.CIRCLE:
        overlay = new this.sdk.Circle(geometry, radius, options)
        break
      case OVERLAY_TYPE.ELLIPSE:
        overlay = new this.sdk.Ellipse(geometry, options)
        break
      case OVERLAY_TYPE.ARCH:
        overlay = new this.sdk.Arch(geometry, radius, options)
        break
      case OVERLAY_TYPE.SECTOR:
        overlay = new this.sdk.Sector(geometry, radius, innerRadius, options)
        break
      case OVERLAY_TYPE.RECT:
        overlay = new this.sdk.Rect(geometry, options)
        break
      case OVERLAY_TYPE.DOT_SYMBOL:
        overlay = new this.sdk.DotSymbol(geometry, image)
        break
      case OVERLAY_TYPE.LINE_POLYGON_SYMBOL:
        const symbolType = `Symbol${codeID}`
        overlay = this.sdk.SymbolFactory.createSymbol(
          symbolType,
          geometry,
          options
        )
        break
      case OVERLAY_TYPE.GRID:
        overlay = new this.sdk.GridPrimitive(geometry, options)
        // overlay.allowDrillPicking = true
        break
      case OVERLAY_TYPE.CHANNEL:
        overlay = this.sdk.SymbolFactory.createSymbol(
          'Symbol25222',
          geometry,
          options
        )
        break
      case OVERLAY_TYPE.HEATMAP:
        overlay = new this.sdk.HeatMapPrimitive({
          points: data,
          clampToGround: options.clampToGround,
          heatmapOptions: {
            radius: options.radius,
            minOpacity: options.minOpacity,
            maxOpacity: options.maxOpacity,
            gradient: options.gradient
          }
        })
        break
      case OVERLAY_TYPE.MARKER_LINE:
        if (options.style) {
          options.style.display = 'balance'
        }
        overlay = new this.sdk.MarkerLine(
          geometry[0],
          geometry[1],
          options.text,
          { ...options }
        )
        break
      case OVERLAY_TYPE.FIRE_OPEN:
        overlay = new this.sdk.FireOpenPrimitive(
          options.firePositions,
          options.targetPosition,
          options
        )
        break
    }
    if (overlay) {
      overlay.bid = id
      overlay.setStyle(style)
      label && label.text && overlay.setLabel(label.text, label.textStyle)
    }

    return overlay
  }

  _getOverlayType(overlay) {
    if (overlay instanceof this.sdk.GridPrimitive) {
      return OVERLAY_TYPE.GRID
    } else if (overlay instanceof this.sdk.Point) {
      return OVERLAY_TYPE.POINT
    } else if (overlay instanceof this.sdk.Polyline) {
      return OVERLAY_TYPE.POLYLINE
    } else if (overlay instanceof this.sdk.Polygon) {
      return OVERLAY_TYPE.POLYGON
    } else if (overlay instanceof this.sdk.Rect) {
      return OVERLAY_TYPE.RECT
    } else if (overlay instanceof this.sdk.Billboard) {
      return OVERLAY_TYPE.BILLBOARD
    } else if (overlay instanceof this.sdk.Circle) {
      return OVERLAY_TYPE.CIRCLE
    } else if (overlay instanceof this.sdk.Ellipse) {
      return OVERLAY_TYPE.ELLIPSE
    } else if (overlay instanceof this.sdk.DotSymbol) {
      return OVERLAY_TYPE.DOT_SYMBOL
    } else if (overlay.type === 'Symbol25222') {
      return OVERLAY_TYPE.CHANNEL
    } else if (this.sdk.SymbolFactory.isMilitarySymbol(overlay.type)) {
      return OVERLAY_TYPE.LINE_POLYGON_SYMBOL
    } else if (overlay instanceof this.sdk.MarkerLine) {
      return OVERLAY_TYPE.MARKER_LINE
    }
  }

  /**
   * 基于图元id获取图元
   * @param {*} id
   */
  getOverlay(id) {
    let overlay
    this.map.eachLayer(layer => {
      const target = layer.getOverlayByBid(id)
      if (target) {
        overlay = target
      }
    })
    return overlay
  }

  /**
   * 基于图元id设置图元属性
   * @param {string} id
   * @param {PointOptions | PolygonOptions | PolylineOptions | RectOptions | CircleOptions | ArchOptions | ChannelOptions
   * | SectorOptions | DotSymbolOptions | LinePolygonSymbolOptions | LabelOptions | ChannelOptions | GridOptions | HeatMapOptions | MarkerLineOptions} options
   */
  setOverlay(id, options = {}) {
    const overlay = this.getOverlay(id)
    const _options = JSON.parse(JSON.stringify(options))
    const { style, geometry, callback, label } = _options
    if (overlay) {
      if (geometry) {
        if (
          [
            OVERLAY_TYPE.POINT,
            OVERLAY_TYPE.DOT_SYMBOL,
            OVERLAY_TYPE.BILLBOARD,
            OVERLAY_TYPE.LABEL
          ].includes(overlay.type)
        ) {
          overlay.position = geometry
        } else {
          overlay.positions = geometry
        }
      }
      if (style) {
        if (typeof style.visible !== 'undefined') {
          style.show = style.visible
        }
        overlay.setStyle(style)
      }

      // 如果是图元设置了闪烁
      if (style.blink) {
        if (typeof overlay._oldShow === 'undefined') {
          overlay._oldShow = overlay.show
        }
        clearInterval(this.blinkListener)
        this.blinkListener = setInterval(() => {
          overlay.show = !overlay.show
        }, 500)
      } else if (typeof style.blink !== 'undefined' && !style.blink) {
        this.blinkListener && clearInterval(this.blinkListener)
        if (overlay._oldShow) {
          overlay.show = true
        }
      }

      if (label) {
        if (typeof label.textStyle?.visible !== 'undefined') {
          label.textStyle.show = label.textStyle.visible
        }
        overlay.setLabel(label.text ?? overlay.labelText, label.textStyle)
      }
      callback && callback({ id: overlay.bid, data: overlay })
    }
  }

  /**
   * 遍历图层的每一个图元对象
   * @param {string} layerId 图层id
   * @param {CallbackFunction} callback 遍历的回调函数
   */
  eachOverlay(layerId, callback) {
    const layer = this.getLayer(layerId)
    if (layer) {
      layer.eachOverlay(overlay => {
        callback({ id: overlay.bid, data: overlay })
      })
    }
  }

  /**
   * 基于图元对象id删除该对象
   * @param {*} id
   */
  removeOverlay(id) {
    const overlay = this.getOverlay(id)
    if (overlay) {
      overlay.remove()
    }
  }

  /**
   * 删除所有的图元对象
   */
  clearOverlays(layerId) {
    if (layerId) {
      const layer = this.getLayer(layerId)
      if (layer) {
        layer.clear()
      }
    } else {
      const layers = this.getLayers()
      layers.forEach(layer => {
        layer?.data?.clear && layer.data.clear()
      })
    }
  }

  /**
   * 绑定Popup弹窗事件
   * @param {string | Object} overlay 图元实体
   * @param {string} context
   * @param {Object} options
   */
  bindPopup(overlay, context, options = {}) {
    if (overlay) {
      overlay.bindPopup(context, options)
    }
  }

  /**
   * 是否显隐 popup弹窗
   * @param overlay 图元
   * @param show 是否显示
   */
  showPopup(overlay, show) {
    if (show) {
      overlay.openPopup()
    } else {
      overlay.closePopup()
    }
  }

  /**
   * 某个图元是否有弹窗
   * @param overlay
   */
  hasPopup(overlay) {
    return overlay?.hasPopup()
  }

  /**
   * 取消绑定图元对象的弹窗
   * @param overlay 图元
   */
  unbindPopup(overlay) {
    overlay && overlay.unbindPopup()
  }

  /**
   * 绑定Popup提示框事件
   * @param {string | Object} overlay 图元实体
   * @param {string} context
   * @param {Object} options
   */
  bindTooltip(overlay, context, options = {}) {}

  /**
   * 取消绑定图元对象的提示框
   * @param overlay 图元
   */
  unbindTooltip(overlay) {}

  /**
   * 绘制图元
   * @param {string} layerId 标绘图元的图层id
   * @param {OVERLAY_TYPE} type 标绘图元类型
   * @param {PointOptions | PolygonOptions | PolylineOptions | RectOptions | CircleOptions | ArchOptions | SectorOptions
   * | DotSymbolOptions | LinePolygonSymbolOptions | ChannelOptions | GridOptions | HeatMapOptions | MarkerLineOptions} options 标绘图元的配置参数
   */
  drawOverlay(layerId, type, options = {}) {
    const layer = this.getLayer(layerId)
    if (layer) {
      if (type.toLowerCase() === 'channel') {
        layer.startDraw('Symbol25222', options)
      } else {
        layer.startDraw(type, options)
      }
    }
  }

  /**
   * 基于图元对象id编辑该图元对象
   * @param {string} id
   * @param {PointOptions | PolygonOptions | PolylineOptions | RectOptions | CircleOptions | ArchOptions |
   * SectorOptions | ChannelOptions | DotSymbolOptions | LinePolygonSymbolOptions | ChannelOptions | GridOptions | HeatMapOptions | MarkerLineOptions} options
   */
  editOverlay(id, options = {}) {
    const overlay = this.getOverlay(id)
    const layer = overlay?.layer
    if (layer) {
      layer.startEditing(overlay, options)
    }
  }

  /**
   * 加载场景
   * @param {Object} json
   */
  loadScene(json) {
    this.map.loadGeoJSON(json)
  }

  /**
   * 导出场景为JSON
   * @return {string} 场景反序列化对象
   */
  exportScene(options) {
    return this.map.toGeoJSON()
  }

  /**
   * @typedef {Object} EventOptions 事件类型配置项
   * @property {EVENT_RANGE} targetType 事件绑定目标类型, global-全局 layer-图层 overlay-图元
   * @property {string | Object} target 事件绑定对象或者id
   * @property {EVENT_TYPE} eventType 绑定的事件类型
   * @property {CallbackFunction} callback 事件调用回调事件
   */

  _getEventType(targetType, eventType) {
    eventType = eventType.toUpperCase()
    targetType = targetType.toLowerCase()
    if (targetType === 'global') {
      if (
        Object.keys(this.sdk.ViewerEventType).includes(eventType.toUpperCase())
      ) {
        eventType = this.sdk.ViewerEventType[eventType]
      } else if (Object.keys(this.sdk.SceneEventType).includes(eventType)) {
        eventType = this.sdk.SceneEventType[eventType]
      }
    } else if (targetType === 'layer') {
      if (Object.keys(this.sdk.LayerEventType).includes(eventType)) {
        eventType = this.sdk.LayerEventType[eventType]
      }
    } else if (targetType === 'overlay') {
      if (Object.keys(this.sdk.OverlayEventType).includes(eventType)) {
        eventType = this.sdk.OverlayEventType[eventType]
      }
    }
    return eventType
  }

  /**
   * 事件绑定（全局、图层、图元）
   * @param eventId {String} 事件的唯一Id
   * @param {EventOptions} options
   */
  bindEvent(eventId, options = {}) {
    let { targetType, target, callback, eventType } = options
    if (!eventId || !callback || !targetType || !eventType) {
      return
    }
    if (Object.keys(this.events).includes(eventId)) {
      this.unbindEvent(eventId)
    }
    eventType = this._getEventType(targetType, eventType)
    this.events[eventId] = {
      callback,
      targetType: targetType,
      eventType: eventType
    }

    const callbackData = obj => {
      if (!obj.overlay) {
        callback({
          id: obj.target?.id,
          data: {
            overlay: obj.target,
            position: obj.wgs84SurfacePosition,
            windowPosition: obj.windowPosition
          }
        })
      } else {
        let id = obj?.overlay?.bid
        if (obj.overlay.length > 1) {
          const firstOverlay = obj.overlay[0]
          if (firstOverlay instanceof star3d.GridPrimitive) {
            id = firstOverlay?.clickGridId
          } else {
            id = firstOverlay?.bid
          }
        } else if (obj.overlay instanceof star3d.GridPrimitive) {
          id = obj?.overlay?.clickGridId
        }
        callback({
          id,
          data: {
            type: this._getOverlayType(obj?.overlay),
            overlay: obj?.overlay,
            position: obj.wgs84SurfacePosition,
            windowPosition: obj.windowPosition
          }
        })
      }
    }
    switch (targetType.toLowerCase()) {
      case 'global':
        this.events[eventId].target = this.map
        this.map.on(eventType, callbackData)
        break
      case 'layer':
        let layer
        if (target instanceof this.sdk.Layer) {
          layer = target
        } else {
          layer = this.getLayer(target)
        }
        if (layer) {
          if (Object.keys(this.sdk.LayerEventType).includes(eventType)) {
            this.events[eventId].target = layer
            layer.on(eventType, callbackData)
          }
        }
        break
      case 'overlay':
        let overlay
        if (target instanceof this.sdk.Overlay) {
          overlay = target
        } else {
          overlay = this.getOverlay(target)
        }
        if (overlay) {
          this.events[eventId].target = overlay
          overlay.on(eventType, callbackData)
        }
        break
    }
  }

  /**
   * 取消一个事件
   * @param {*} eventId 事件id
   */
  unbindEvent(eventId) {
    const info = this.events[eventId]
    if (info) {
      info.target && info.target.off(info.eventType, info.callback)
      this.events[eventId] = undefined
      delete this.events[eventId]
    }
  }

  /**
   * 二三维切换
   * @param {SCENE_MODE} mode 2/3/2.5 - 二维/三维/2.5维
   */
  changeSceneMode(mode) {
    this.map.changeSceneMode(mode)
  }

  /**
   * 大地坐标转屏幕坐标
   */
  wgs84ToWindowCoordinates(wgs84) {
    return this.sdk.Transform.transformWGS84ToWindow(wgs84, this.map)
  }

  /**
   * 获取当前是二维还是三维
   * @return {SCENE_MODE} 当前场景模式
   */
  getSceneMode() {
    if (this.map.scene.mode === 2) {
      return SCENE_MODE.SCENE2D
    } else if (this.map.scene.mode === 3) {
      return SCENE_MODE.SCENE3D
    }
  }

  /**
   * 获取地图对象
   * @returns
   */
  getMap() {
    return this.map
  }

  /**
   * 获取GIS引擎对象
   * @returns
   */
  getSdk() {
    return this.sdk
  }

  /**
   * 获取canvas画布
   * @return {*}
   */
  getCanvas() {
    return this.map.scene.canvas
  }
}

/**
 * 全局挂载
 */
const mapAbstractInstance = new ZXMapMiddleware()
mapAbstractInstance.Util = Util
mapAbstractInstance.COLOR_GRADIENT = ColorGradient
mapAbstractInstance.OVERLAY_TYPE = OVERLAY_TYPE
mapAbstractInstance.LAYER_TYPE = LAYER_TYPE
mapAbstractInstance.EVENT_TYPE = EVENT_TYPE
mapAbstractInstance.EVENT_RANGE = EVENT_RANGE
mapAbstractInstance.MATERIAL_TYPE = MATERIAL_TYPE
mapAbstractInstance.SCENE_MODE = SCENE_MODE

/**
 * 全局挂载
 * @type {ZXMapMiddleware}
 */
window.MapInstance = mapAbstractInstance
